package us.versus.them.b3ge.minigames.cards.poker;

import us.versus.them.b3ge.minigames.cards.Card;
import us.versus.them.b3ge.minigames.cards.Deck;

class PokerHandEvaluator {
	public static var SCORE_VALUE = [ 
		0, 4096, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048 
	];

	public static function tmi( hand:Array< Card > ) : PokerHandEvaluation {
		return new PokerHandEvaluation( hand ).evaluate();
	}

	public inline static function countValues( hand:Array< Card > ) : Array< Int > { 
		var count = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
		for ( card in hand ) {
			count[ card.value - 1 ]++;
		}
		return count;
	}
	
	public inline static function scoreHand( rank:PokerHand, hand:Array< Card > ) : PokerHandScore {
		var score = new PokerHandScore( rankToScore( rank ) );
		for ( card in hand ) {
			if ( card.used ) {
				score.used += SCORE_VALUE[ card.value ];
			} else {
				score.unused += SCORE_VALUE[ card.value ];
			}
		}
		return score;
	}

	public inline static function rankToScore( hand:PokerHand ) : Int {
		var f = 0;
		switch ( hand ) {
			case StraightFlush: f = 8;
			case FourOfAKind:   f = 7;
			case FullHouse:     f = 6;
			case Flush:         f = 5;
			case Straight:      f = 4;
			case ThreeOfAKind:  f = 3;
			case TwoPair:       f = 2;
			case OnePair:       f = 1;
			case Junk:          f = 0;
		}
		return f;
	}

	public inline static function evaluate( hand:Array< Card > ) : PokerHand {
		return evaluateHand( hand, countValues( hand ) );
	}

	public static function evaluateHand( hand:Array< Card >, counts:Array< Int > ) : PokerHand {
		if ( isStraightFlush( hand, counts ) ) return PokerHand.StraightFlush;
		if ( isFourOfAKind(   hand, counts ) ) return PokerHand.FourOfAKind;
		if ( isFullHouse(     hand, counts ) ) return PokerHand.FullHouse;
		if ( isFlush(         hand, counts ) ) return PokerHand.Flush;
		if ( isStraight(      hand, counts ) ) return PokerHand.Straight;
		if ( isThreeOfAKind(  hand, counts ) ) return PokerHand.ThreeOfAKind;
		if ( isTwoPair(       hand, counts ) ) return PokerHand.TwoPair;
		if ( isOnePair(       hand, counts ) ) return PokerHand.OnePair;
		return PokerHand.Junk;
	}

	public inline static function isStraightFlush( hand:Array< Card >, counts:Array< Int > ) : Bool {
		var straightFlush = isFlush( hand, counts ) && isStraight( hand, counts );
		return useAll(
			isFlush( hand, counts ) && isStraight( hand, counts )
			, hand
		);
	}

	public inline static function isFourOfAKind( hand:Array< Card >, counts:Array< Int > ) : Bool {
		var fourOfAKind = false;
		for ( value in 0 ... counts.length ) {
			var count = counts[ value ];
			if ( 4 == count ) {
				for ( card in hand ) {
					card.used = ( value == card.value - 1 );
				}
				fourOfAKind = true;
				break;
			}
		}
		return fourOfAKind;
	}

	public inline static function isFullHouse( hand:Array< Card >, counts:Array< Int > ) : Bool {
		var has3 = false;
		var has2 = false;
		for ( count in counts ) {
			if ( 3 == count ) has3 = true;
			if ( 2 == count ) has2 = true;
		}
		return useAll( has3 && has2, hand );
	}

	public inline static function isFlush( hand:Array< Card >, counts:Array< Int > ) : Bool {
		var match = true;
		var suit:Suit = null;
		for ( card in hand ) {
			if ( null == suit ) {
				suit = card.suit;
			} else {
				if ( card.suit != suit ) {
					match = false;
					break;
				}
			}
		}
		return useAll( match, hand );
	}

	public inline static function useAll( match:Bool, hand:Array< Card > ) {
		if ( match ) {
			for( card in hand ) {
				card.used = true;
			}
		}
		return match;
	}

	public inline static function isStraight( hand:Array< Card >, counts:Array< Int > ) : Bool {
		var straight = false; // haha
		for ( i in 0 ... counts.length - 3 ) {
			if ( 1 == counts[ i ] ) {
				if (
					   1 == counts[ i + 1 ]
					&& 1 == counts[ i + 2 ]
					&& 1 == counts[ i + 3 ]
					&& 1 == counts[ ( i + 4 ) % counts.length ] // ace high
				) {
					straight = useAll( true, hand ); // hmm
					break;
				}
				if ( 0 != i ) break; // didn't start with ace
			}
		}
		return straight;
	}

	public inline static function isThreeOfAKind( hand:Array< Card >, counts:Array< Int > ) : Bool {
		var threeOfAKind = false;
		for ( value in 0 ... counts.length ) {
			var count = counts[ value ];
			if ( 3 == count ) {
				for( card in hand ) {
					card.used = ( value == card.value - 1 );
				}
				threeOfAKind = true;
				break;
			}
		}
		return threeOfAKind;
	}

	public inline static function isTwoPair( hand:Array< Card >, counts:Array< Int > ) : Bool {
		var pair1 = -1;
		var pair2 = -1;
		for ( value in 0 ... counts.length ) {
			var count = counts[ value ];
			if ( 2 == count ) {
				if ( -1 == pair1 ) {
					pair1 = value;
				} else {
					pair2 = value;
				}
			}
		}
		var twoPair = false;
		if ( -1 != pair2 ) {
			twoPair= true;
			for( card in hand ) {
				card.used = ( pair1 == card.value - 1 ) || ( pair2 == card.value - 1 );
			}
		}
		return twoPair;
	}

	public static inline function isOnePair( hand:Array< Card >, counts:Array< Int > ) : Bool {
		var onePair = false;
		for ( value in 0 ... counts.length ) {
			var count = counts[ value ];
			if ( 2 == count ) {
				for( card in hand ) {
					card.used = ( value == card.value - 1 );
				}
				onePair = true;
				break;
			}
		}
		return onePair;
	}

	public static function oneHand( hand:Array< Card > ) {
		trace( tmi( hand ) );
	}

	public static function main() {
		var deck = new Deck();
		var hands = new Array< Array< Card > >();
		for ( i in 0 ... 5000 ) {
			deck.shuffle();
			hands.push( deck.dealHand( 5 ) );
		}

		var then = Date.now().getTime();
		for ( hand in hands ) {
			tmi( hand ); // this is quiet
		}
		var now = Date.now().getTime();
		var diff = now - then;
		trace( 'evaluated ' + hands.length + ' in ' + diff + ' seconds' );
#if flash
		var txt = new flash.text.TextField();
		txt.text = 'click here to evaluate a new hand';
		txt.backgroundColor = 0xAAAADD;
		txt.background = true;
		txt.selectable = false;
		txt.autoSize = flash.text.TextFieldAutoSize.LEFT;
		txt.y = 123;
		txt.addEventListener(
			flash.events.MouseEvent.CLICK
			, function( _ ) {
				deck.shuffle();
				var hand = deck.dealHand( 5 );
				var tmi = new PokerHandEvaluation( hand );
				tmi.evaluate();
				haxe.Log.clear();
				trace( ''
					+ '\nscore : ' + tmi.score
					+ '\nrank  : ' + tmi.rank
					+ '\nhand  : ' + tmi.hand
				);
			}
		);
		flash.Lib.current.addChild( txt );
#else
		deck.shuffle();
		var handz = new Array< PokerHandEvaluation >();
		for ( i in 0 ... 5 ) {
			var hand = new Array< Card >();
			for ( j in 0 ... 5 ) hand.push( deck.next() );
			handz.push( new PokerHandEvaluation( hand ).evaluate() );
		}

		handz.sort( PokerHandEvaluation.compare );
		for ( hand in handz ) trace( hand );
#end
	}
}
